package socket5

import (
	"errors"
	"fmt"
	"go-socket5-proxy/helper"
	"io"
	"log"
	"net"
)

type Server interface {
	Run() error
}

type Socket5Server struct {
	Port int64
	IP   string
}

func (s *Socket5Server) Run() error {
	address := fmt.Sprintf("%s:%d", s.IP, s.Port)
	fmt.Printf("%s:%d \n", s.IP, s.Port)
	listen, err := helper.CreateListen(address)
	if err != nil {
		log.Fatal(err)
		return err
	}

	for {
		conn, err := listen.Accept()
		if err != nil {
			log.Printf("连接失败:%s: %s", conn.RemoteAddr(), err)
		}

		go func() {
			defer conn.Close()
			err = handleConnection(conn)
			if err != nil {
				//log.Printf("处理连接 handleConnection %s: %s", conn.RemoteAddr(), err)
			}

		}()

	}
}

// 处理连接 handleConnection
func handleConnection(conn net.Conn) error {

	//协商过程
	if err := auth(conn); err != nil {
		return err
	}

	//请求过程
	targetConn, err := request(conn)
	if err != nil {
		return err
	}
	if targetConn == nil {
		return nil
	}
	//转发过程
	return forward(conn, targetConn)
}

func forward(conn net.Conn, targetConn net.Conn) error {
	defer targetConn.Close()
	go io.Copy(targetConn, conn)
	_, err := io.Copy(conn, targetConn)
	return err
}

func request(conn net.Conn) (net.Conn, error) {
	message, err := NewClientRequestMessage(conn)
	if err != nil {
		return nil, err
	}

	//检查命令是否支持
	if message.CMD != CmdConnect {
		return nil, WriteRequestFailureMessage(conn, ReplyCommandNotSupported)
	}

	//ipv6
	if message.AddressType == TypeIPV6 {
		return nil, WriteRequestFailureMessage(conn, ReplyAddressTypeNotSupported)
	}

	//请求访问目标服务器
	addres := fmt.Sprintf("%s:%d", message.Address, message.Port)
	//fmt.Printf("请求访问目标服务器:%v\n", message.AddressType)
	targetConn, err := net.Dial("tcp", addres)
	if err != nil {
		return nil, WriteRequestFailureMessage(conn, ReplyConnectionRefused)
	}

	//发送成功
	addrValue := targetConn.LocalAddr()
	addr := addrValue.(*net.TCPAddr)

	if targetConn == nil {
		fmt.Printf("targetConn is nil :" + addr.IP.String())
	}

	return targetConn, WriteRequestSuccessMessage(conn, addr.IP, uint16(addr.Port))
}

func auth(conn net.Conn) error {
	client, err := NewClientAuthMessage(conn)
	if err != nil {
		return err
	}
	//log.Println(client.Version, client.NMethods, client.Methods)

	// 是否支持 no-auth
	var accept_table bool
	for _, method := range client.Methods {
		if method == Method_NO_AUTH {
			accept_table = true
		}
	}

	if !accept_table {
		NewServerAuthMessage(conn, Method_NO_ACCEPT_TABLE)
		return errors.New("方法不支持 not auth")
	}

	return NewServerAuthMessage(conn, Method_NO_AUTH)
}
